window['djp'] = (
/** 
 * @param {undefined=} UNDEFINED
 * @return {function(*=)}
 */
function( haveAddEventListener, UNDEFINED ){
  var
  makeToggleAttach = haveAddEventListener ?
  function(node, handler, eventType, useCapture){
    var eventTypes = eventType.split(' ');
    return function(on){
      for(var i = eventTypes.length; i--;){
        node[on ? 'addEventListener' : 'removeEventListener'](
          eventTypes[i], handler, useCapture
        )
      }
    }
  }:
  function(node, handler, eventType){
    var eventTypes = eventType.split(' ');
    return function(on){
      for(var i = eventTypes.length; i--;){
        node[on ? 'attachEvent' : 'detachEvent']('on'+eventTypes[i], handler)
      }
    }
  };
  /**
   * Создает новый метод more, для текущего экземплра DJProxy в цепочке. Вспомогательная функция.
   * @param {DJProxy} pastThis текущий экземпляр DJProxy в цепочке
   * @param {function(DJProxy,*,*,boolean):boolean} chkFunc возвращает true, если элемент проходит проверку в этом звене цепочки. Также может менять элемент этого звена.
   * @param {*=} a необязательный первый передаваемый параметр
   * @param {*=} b необязательный второй передаваемый параметр
   * @return {DJProxy}следующий новый экземпляр DJProxy в цепочке
   */
  function moreMaker(pastThis, chkFunc, a, b){
    var r = new DJProxy(pastThis), isFirst = true;
    /**
     * Возвращает обертку следующего, проходяшего цепочку отбора, элемента.
     * @this {DJProxy}
     * @return {DJProxy}
     */
    r.more = function(){
      while (this.node && !chkFunc(this, a, b, isFirst)){
        this.node = pastThis.more().node;
        isFirst = true;
      }
      isFirst = false;
      return this
    }
    return r.more()
  }
  /**
   * Обертка события.
   * @constructor
   */
  function DJPEvent(e, fToggleAttach){
    var $this = this;
    $this.e = e;
    $this.$ie = haveAddEventListener || {
      $button: e.button,
      'X': e.clientX,
      'Y': e.clientY,
      $propertyName: e.propertyName
    };
    $this.$type = e.type;
    $this.$keyCode = e.keyCode || UNDEFINED;
    $this.$toElement = e.toElement || UNDEFINED;
    $this.$fromElement = e.fromElement || UNDEFINED;
    $this.$relatedTarget = e.relatedTarget || UNDEFINED;
    $this.toggleOn = fToggleAttach;
  }
  DJPEvent.prototype.$which = function (){
    var k;
    return haveAddEventListener ? 
    this.e.which :
    (k = this.$ie.$button) !== UNDEFINED ?
    k & 1 ?
    1 : (
      k & 2 ?
      3 : (
        k & 4 ?
        2 :
        0
      )
    ) :
    UNDEFINED;
  };
  DJPEvent.prototype.prevent = function (){
    DJPEvent.prototype.prevent = haveAddEventListener ?
    function (){
      this.e.preventDefault()
    } :
    function (){
      this.e.returnValue = false
    };
    this.prevent()
  };
  DJPEvent.prototype.pos = function (){
    DJPEvent.prototype.pos = haveAddEventListener ?
    function (){
      return {x: this.e.pageX, y: this.e.pageY}
    } :
    function (){
      var
      t = document.documentElement,
      e = this.$ie,
      o = {'X':'Left','Y':'Top'},
      k,
      scroll = 'scroll',
      result = {};
      for (k in o){
        result[k] = e[k]+t[scroll+o[k]]
      }
      if (t = document.body) for (k in o){
        result[k] += t[scroll+o[k]]
      }
      return {x: result['X'], y: result['Y']}
    };
    return this.pos()
  };
  /**
   * Обертка элемента.
   * @constructor
   */
  function DJProxy(p){
    var n=document;
    if(p){
      if (p.constructor != DJProxy) {
        this.node = p.constructor != String ? p : n.getElementById(p)
      } else {
        this.node = p.node
      }
    } else {
      /* если параметр не задан, оборачиваем элемент перед текущим тегом script */
      while(n && n.nodeName!='SCRIPT')
        n=n[n['firebugIgnore']?'previousSibling':'lastChild'];
      while(n && (n.nodeType!=1 || n.nodeName=='SCRIPT'))n=n.previousSibling;
      this.node = n
    }
  }
  function oDJProxy(p){
    return new DJProxy(p)
  }
  DJProxy.prototype = {
    /**
     * заполняет элемент текстом, если текст не задан возвращает весь текст в элементе.
     * @this {DJProxy}
     * @param {string=} sValue
     * @return {string|DJProxy}
     */
    text: (typeof document.documentElement.innerText != 'undefined') ?
    function(sValue){
      return sValue === UNDEFINED ?
        this.node.innerText :
        (this.node.innerText = sValue, this)
    } :
    function(sValue){
      return sValue === UNDEFINED ?
        this.node.textContent :
        (this.node.textContent = sValue, this)
    },
    /**
     * Добавляет элементы перед закрытием текущего.
     * @this {DJProxy}
     * @param {string|DJProxy} value текст HTML, добавляемого фрагмента или обретка элемента.
     * @return {DJProxy} обертка последнего добавленного элемента
     */
    append: function(value){
      var res, node = this.node, r;
      res = value.constructor == DJProxy ?
      value.node :
      document.createRange ? (
        r = node.ownerDocument.createRange(),
        r['selectNodeContents'](node),
        r.collapse(false),
        r['createContextualFragment'](value)
      ) : (
        node['insertAdjacentHTML']('beforeend', value)
      );
      res && node.insertBefore(res, null);
      return new DJProxy(node.lastChild)
    },
    /**
     * Возвращает или задает атрибуту элемента значение из второго параметра.
     * @this {DJProxy}
     * @param {string} sAttrName название атрибута
     * @param {string=} sValue новое значение атрибута
     * @return {string|undefined}
     */
    attr: function(sAttrName, sValue){
      var node = this.node;
      return sValue === UNDEFINED ?
        node.nodeType == 1 && node.getAttribute(sAttrName, 2) || (
          node.getAttributeNode && (
            node = node.getAttributeNode(sAttrName)
          ) ? node.nodeValue : ''
        ) :
        (node.setAttribute(sAttrName, sValue), this)
    },
    /**
     * Возвращает true элемент содержит в себе или сам является элементом, пердеданным в аргументе.
     * @this {DJProxy}
     * @param {DJProxy} oDJProxy проверяемый элемент
     * @return {boolean}
     */
    contains: document.documentElement.contains ?
    function(oDJProxy){
      return this.node && oDJProxy.node && (
        this.node == oDJProxy.node || this.node.contains(oDJProxy.node)
      )
    }:
    function (oDJProxy){
      return this.node && oDJProxy.node && (
        this.node == oDJProxy.node || (this.node.compareDocumentPosition(oDJProxy.node) & 16)
      )
    },
    /**
     * Возвращает true если у элемента есть такой стиль.
     * @this {DJProxy}
     * @param {string} sClassName название стиля
     * @return {boolean}
     */
    classIs: function(sClassName){
      return this.node && ( (' '+this.node.className+' ').indexOf(' '+sClassName+' ') + 1 )
    },
    /**
     * TODO надо проверить.
     * Переключает/включает/выключает/заменяет классы стиля у элемента.
     * @this {DJProxy}
     * @param {string} sClassNames названия классов стиля
     * @param {boolean|string|undefined} on включить, если true
     * или выключить если false перечисленные классы. Либо, если строковый тип,
     * удалить названия классов sClassNames и вставить классы из on, если их еще нет.
     * @return {DJProxy}
     */
    toggleClass: function(sClassNames, on){
      var
      node = this.node,
      onIsString = on.constructor == String,
      aReplacedClasses = (sClassNames + (onIsString ? ' ' + on : '')).replace(/\s+/,' ').split(' '),
      i = aReplacedClasses.length,
      a = (' ' + node.className + ' ').replace(/\s+/,' ').split(' ' + aReplacedClasses[--i] + ' '),
      c = a.length > 1;
      for(;i--;){
        a = (' ' + a.join(' ') + ' ').split(' ' + aReplacedClasses[i] + ' ');
        c |= a.length > 1
      }
      a = a.join(' ');
      if(on !== false){
        node.className = a + (onIsString ? on : sClassNames);
      } else if(c){
        node.className = a;
      }
      return this
    },
    /**
     * Возвращает обертку следующего, проходяшего цепочку отбора, элемента.
     * @this {DJProxy}
     * @return {DJProxy}
     */
    more: function(){
      this.node = null;
      return this
    },
    /**
     * отбирает элемент с заданным названием узла.
     * @this {DJProxy}
     * @param {string} sNodeName название узла (заглавными буквами http://www.quirksmode.org/dom/w3c_core.html#t20)
     * @return {DJProxy}
     */
    hasNode: function(sNodeName){
      return moreMaker(
        this,
        function(t, sNodeName, b, isFirst){
          return isFirst && t.node.nodeName == sNodeName
        },
        sNodeName
      )
    },
    /**
     * отбирает элемент с подходящим значением атрибута.
     * @this {DJProxy}
     * @param {string} sAttrName название атрибута
     * @param {string|RegExp} expr значение атрибута, или регулярное выражение
     * @return {DJProxy}
     */
    hasAttr: function(sAttrName, expr){
      return moreMaker(
        this,
        function(t, sAttrName, expr, isFirst){
          return isFirst && (expr.test ?
            expr.test(t.attr(sAttrName)) :
            (expr == t.attr(sAttrName)))
        },
        sAttrName, expr
      )
    },
    /**
     * отбирает элемент имеющий заданный класс.
     * @this {DJProxy}
     * @param {string} sValue название класса
     * @return {DJProxy}
     */
    hasClass: function(sValue){
      return moreMaker(
        this,
        function(t, sValue, b, isFirst){
          return isFirst && (' '+t.node.className+' ').indexOf(' '+sValue+' ') != -1
        },
        sValue
      )
    },
    /**
     * отбирает сначала текущий элемент, а затем родительские.
     * @this {DJProxy}
     * @return {DJProxy}
     */
    ancestor: function(){
      return moreMaker(
        this,
        function(t, a, b, isFirst){
          return isFirst ? t.node : t.node = t.node.parentNode
        }
      )
    },
    /**
     * отбирает предидущий элемент, из соседних.
     * @this {DJProxy}
     * @return {DJProxy}
     */
    next: function(){
      return moreMaker(
        this,
        function(t){
          for(
            t.node = t.node.nextSibling;
            t.node && t.node.nodeType != 1;
            t.node = t.node.nextSibling
          );
          return t.node
        }
      )
    },
    /**
     * отбирает следующий элемент, из соседних.
     * @this {DJProxy}
     * @return {DJProxy}
     */
    prev: function(){
      return moreMaker(
        this,
        function(t){
          for(
            t.node = t.node.previousSibling;
            t.node && t.node.nodeType != 1;
            t.node = t.node.previousSibling
          );
          return t.node
        }
      )
    },
    /**
     * отбирает сначала первый дочерний элемент.
     * @this {DJProxy}
     * @return {DJProxy}
     */
    child: function(){
      return moreMaker(
        this,
        function(t,a,b,isFirst){
          for(
            t.node = isFirst ? t.node.firstChild : t.node.nextSibling;
            t.node && t.node.nodeType != 1;
            t.node = t.node.nextSibling
          );
          return t.node
        }
      )
    },
    /**
     * подключает к элементу обработчик события.
     * @this {DJProxy}
     * @param {string} eventType тип события
     * @param {function(DJProxy, {X: Number, Y: Number, which: Number, prevent: function(), toggleOn: function(boolean),keyCode: Number, relatedTarget: Element, toElement: Element, fromElement: Element})} callback функция вызываемая при событии
     * @param {boolean=} useCapture если установлен, то по возможности сработает на этапе просачивания события.
     * @return {function(boolean)} функция повторно отключающая/подклчающая callback к этому событию.
     */
    attach: haveAddEventListener ?
    function(eventType, callback, useCapture){
      var
      handler = function(e){
        if((e.type!='click' && e.type!='mousedown') || !e.button) {
          var r = new DJProxy(e.target);
          callback(r,  new DJPEvent(e, fToggleAttach))
        }
      },
      fToggleAttach = makeToggleAttach(this.node, handler, eventType, useCapture);
      fToggleAttach(true);
      return fToggleAttach
    } :
    function(eventType, callback, useCapture){
      var handler = function(){
        var e = window.event,
        r = new DJProxy(e.srcElement || document);
        callback(r, new DJPEvent(e, fToggleAttach))
      },
      fToggleAttach = makeToggleAttach(this.node, handler, eventType);
      fToggleAttach(true);
      return fToggleAttach
    },
    /**
     * Возвращает или задает интервал начала и конца выделенного текста.
     * @this {DJProxy}
     * @param {{begin:integer,end:integer}|undefined} pos интервал начала и конца выделенного текста
     * @return {{begin:integer,end:integer}|DJProxy}
     */
    selectionRange: typeof window.getSelection != 'undefined'?
    function(pos){
      return pos === UNDEFINED ? {
        begin: this.node.selectionStart,
        end: this.node.selectionEnd
      } : (
        this.node.selectionStart = pos.begin,
        this.node.selectionEnd = pos.end,
        this
      )
    }:
    function(pos){
      var
      range = document.selection.createRange(),
      textRange = range.duplicate(),
      result = {begin:0, end:0},
      character = 'character',
      withoutNewLineCount;
      textRange.moveToElementText(this.node);
      if(pos === UNDEFINED){
        if(textRange.parentElement().contains(range.parentElement())){
          textRange.setEndPoint('EndToStart', range);
          result.begin = textRange.text.length;
          textRange.setEndPoint('EndToEnd', range);
          result.end = textRange.text.length;
        }
      } else {
        withoutNewLineCount = textRange.text.slice(0, pos.end).replace(/\n/g,'').length;
        textRange.collapse(!0);
        textRange.moveEnd(character, withoutNewLineCount);
        range.moveToElementText(this.node);
        withoutNewLineCount = range.text.slice(0, pos.begin).replace(/\n/g,'').length;
        range.collapse(!0);
        range.moveEnd(character, withoutNewLineCount);
        textRange.setEndPoint('StartToEnd', range);
        textRange.select();
        result = this
      }
      return result
    }
  };
  DJProxy.prototype.constructor = DJProxy;
  
  var getXMLHttpRequest = !window.XMLHttpRequest ?
  function() {
    return new ActiveXObject('Microsoft.XMLHTTP');
  } :
  function() {
    return new XMLHttpRequest();
  };
  oDJProxy.send = function(url, callback, postData){
    var
    request = getXMLHttpRequest(),
    t = /^(\w+:)?\/\/([^\/?#]+)/.exec(url),
    h={
      'X-Requested-With': [
        !t || (t[1] == location.protocol && t[2] == location.host),
        'XMLHttpRequest'
      ]/*,
      'Connection': [request['overrideMimeType'],'close']*/
    }, k, isDone = 0;
    setTimeout(
      function(){isDone || request.abort()},
      oDJProxy.send.timeOut
    );
    if (postData){
      h['Content-type'] = [1,'application/x-www-form-urlencoded']
    }
    request['open']( postData !== UNDEFINED ? 'POST': 'GET', url, true);
    request['onreadystatechange'] = (
      function(){
        var notready = 1; //fix duble execute
        return function() {
          if(request['readyState'] == 4 && notready) {
            notready = 0;
            isDone = 1;
            callback(request['responseText'], request)
          }
        }
      }
    )(this);
    t = oDJProxy.send.requestHeaders;
    for (k in h)
      if(t[k] === UNDEFINED && h[k][0])
        t[k] = h[k][1]
    for (k in t) {
      request['setRequestHeader'](k, t[k])
    }
    request['send'](postData !== UNDEFINED ? postData : null)
  }
  oDJProxy.send.requestHeaders = {};
  oDJProxy.send.timeOut = 30000;
  oDJProxy.getJSON = function(url, callback, postData){
    oDJProxy.send(
      url,
      function(s, r){
        if(r.status >= 200 && r.status < 300){
          var s=r.responseText,res=!(
            /[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(
              s.replace(
                /(\\*)"[^"]*?(\\*)"/g,
                function(a,b,c){
                  return (b.length%2 ^ c.length%2) ? '"' : ''
                }
              ).replace(/"[^"]*"/g,'')
            )
          ) && (new Function(['return(',s,')'].join('')))()
        }
        callback(res, r)
      },
      postData
    )
  };
  
  return oDJProxy
})(document.addEventListener);
 /**
   * возвращает координаты элемента, относительно документа.
   * @this {DJProxy}
   * @return {{top:Number,left:Number}}
   */
window['djp'](null).constructor.prototype.offset = document.documentElement.getBoundingClientRect?
function(){
  if(this.node){
    var doc = this.node.ownerDocument,
    rootNode = doc.documentElement,
    box = this.node.getBoundingClientRect(),
    result={'Left':box['left'],'Top':box['top']},
    k, t;
    for (k in result){
      t='scroll'+k;
      result[k] = parseInt(
        result[k] + Math.max( rootNode[t], doc.body[t] ) - rootNode['client'+k],
        10
      )
    }
    return {
      top: result['Top'],
      left: result['Left']
    }
  }
}:
function(){
  if(this.node){
    var
    elem = this.node,
    doc = elem.ownerDocument,
    rootNode = doc.documentElement,
    body = doc.body,
    getStyle=doc.defaultView.getComputedStyle,
    results={top:0, left:0},
    parent = elem, 
    offsetChild = elem,
    offsetParent = elem,
    userAgent = navigator.userAgent.toLowerCase(),
    safari = /webkit/.test( userAgent ),
    mozilla = /mozilla/.test( userAgent ) && !/(compatible|webkit)/.test( userAgent ),
    safari2 = safari && parseInt((userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [])[1]) < 522 && !/adobeair/i.test(userAgent),
    fixed;
    function add(l, t, o) {
      o.left += parseInt(l, 10) || 0;
      o.top += parseInt(t, 10) || 0;
    }
    function border (elem, o) {
      var style=elem.ownerDocument.defaultView.getComputedStyle(elem,null);
      add( style['borderLeftWidth'], style['borderTopWidth'], o)
    };
    t = getStyle(elem, null)['borderLeftWidth']
    // Initial element offsets
    add( elem.offsetLeft, elem.offsetTop, results );
    // Get parent offsets
    while ( offsetParent = offsetParent.offsetParent ) {
      // Add offsetParent offsets
      add( offsetParent.offsetLeft, offsetParent.offsetTop, results );
      // Mozilla and Safari > 2 does not include the border on offset parents
      // However Mozilla adds the border for table or table cells
      if ( mozilla && !/^T(ABLE|D|H)$/.test(offsetParent.nodeName) || safari && !safari2 )
        border( offsetParent, results );
      // Add the document scroll offsets if position is fixed on any offsetParent
      if ( !fixed && getStyle(offsetParent, null)['position'] == "fixed" )
        fixed = true;
      // Set offsetChild to previous offsetParent unless it is the body element
      offsetParent!=body && (offsetChild=offsetParent)
    }
    // Get parent scroll offsets
    while ( (parent = parent.parentNode) && parent != body && parent != rootNode) {
      // Remove parent scroll UNLESS that parent is inline or a table to work around Opera inline/table scrollLeft/Top bug
      if ( !/^inline|table.*$/i.test(getStyle(parent, null)['display']) )
        // Subtract parent scroll offsets
        add( -parent.scrollLeft, -parent.scrollTop, results );
      // Mozilla does not add the border for a parent that has overflow != visible
      if ( mozilla && getStyle(parent, null)['overflow'] != "visible" )
        border( parent, results );
    }
    // Safari <= 2 doubles body offsets with a fixed position element/offsetParent or absolutely positioned offsetChild
    // Mozilla doubles body offsets with a non-absolutely positioned offsetChild
    if ( (safari2 && (fixed || getStyle(offsetChild, null)['position'] == "absolute")) || 
      (mozilla && getStyle(offsetChild, null)['position'] != "absolute") )
        add( -body.offsetLeft, -body.offsetTop, results );
    // Add the document scroll offsets if position is fixed
    if ( fixed )
      add(Math.max(doc.documentElement.scrollLeft, body.scrollLeft, results),
        Math.max(doc.documentElement.scrollTop,  body.scrollTop));
    // Return an object with top and left properties
    return {
      top: results.top,
      left: results.left
    }
  }
};